隨著微前端應用增加,每一個個體都會打包一些既定的函式庫。跟微服務不同,微前端需要讓客戶承擔流量上的負擔,可能造成額外的流量開支與效能開支,盡可能要去重複應用使用過的。所以像是微服務單純的切分,微前端更要經常煩惱「共用」議題。
微前端的模組共用就一定要提到模塊聯邦( Module Federation ,往後簡稱 MF )!
MF 並不是一個套件,而是一種多模組的管理系統的思維,只是有套件實現了這種思維。MF 的理念基本上貫穿所有「模組共用」的思維,使用多服務進入點管理多版本的 packages,具備版本識別與切分環境的能力,讓多個組態情境能良好管理各自的環境版本。目前其實幾乎找不到比起 MF 更完善的其他解決方案,幾乎仰賴大量的基礎建設架構的實作實現,我個人還是建議就用吧。
如果要細談 MF 可能完全可以開另一個章章節,所以就不打算在這邊提及其實作與原理,還有其相關特性。
但也不得不提及,一旦使用了 MF,模組化就會被綁定在 特定的 chunk system ,遷移上變成整個架構的成本。微前端雖然有分散部署的優點,但架構面的部分仰賴一致的規範與協定,如果一致的部分發生變動,那就會根本性的需要大幅度調整。
自 Webpack 提出了 MF 的實作概念,這幾乎成為了一個標準,漸漸的都以這套做法為基準。網路上的資源也幾乎是 Webpack Module Federation 相關資訊為大宗,可以找到最豐富的資源。
隨著 Vite 優秀的打包能力與高速的 Hot Reload 特性,Vite 可以說是制霸整個前端生態圈。截至 2024 年,Vite 使用率已經追上 Webpack 的一半,可以說是非常強大。但 Vite 卻在 Module Federation 這部分可以說是非常弱,基於它特殊的特性,很難去實作出 MF 的處理機制,就算是 Plugin 也開始支援,也無法解決 dev mode 無法啟用 MF 的問題。當前 Vite 無法在開發環境動態處理 MF 的加載和熱更新,依然需要很多人工成分。
目前 Vue.js 開發者 Evan You 在新開發的打包工具 Rolldown
宣布會支援,並提供官方解決方案,根本處理 MF 的問題,當前生態圈進入被動等待的局勢,這個時期可說是非常不適合使用 Vite 作為微前端主力的打包工具。
這是由Infra Architect。Zack Jackson所維護的 Repository,抽離打包工具的耦合,自成的解決方案。
最大的特點是提供了 Dynamic Module Federation,可以完全不依賴任何打包工具完成 MF 的機制實踐,也跟各種打包工具做了一定深度的整合。
我以 React 做舉例,如何正確設定 MF 的共享。
網路上的資訊都會叫你共享 react
, react-dom
這兩包,事實上是不夠的,因為在實作上還會多使用 react/jsx-runtime
和 react-dom/client
兩個分包,如果不一起共享也會視為不同的套件包。所以實際上實作你必需把所有使用到的東西宣告清楚,正確共享。
const ModuleFederationPlugin = require("./module-federation");
const packageJson = require("package.json");
module.exports = {
// ...impl config
plugins: [
new ModuleFederationPlugin({
// ...impl config
shared: {
react: {
singleton: true,
requiredVersion: packageJson.dependencies["react"],
},
"react/jsx-runtime": {
singleton: true,
requiredVersion: packageJson.dependencies["react"],
},
"react-dom": {
singleton: true,
requiredVersion: packageJson.dependencies["react-dom"],
},
"react-dom/client": {
singleton: true,
requiredVersion: packageJson.dependencies["react-dom"],
},
},
}),
],
};
剛開始使用 MF 的新手最要命的是不分青紅皂白的共享。
const ModuleFederationPlugin = require("./module-federation");
const packageJson = require("package.json");
module.exports = {
// ...impl config
plugins: [
new ModuleFederationPlugin({
// ...impl config
shared: {
...packageJson.dependencies,
},
}),
],
};
當然這是很省事,但如果要做這種事不如別用微前端了,這樣會造成幾個問題:
tree shaking
,會造成過多冗余的程式碼進入整個 Bundle 中,最終造成網站應用程式過於肥大。MF 共享套件其實就已經解決很多微前端議題,但不代表就沒問題,因為微前端最頭痛的依然是跨模組溝通。套件共享上最大的議題就是版本,如何讓跨版本能有效互動就成為共享套件要面臨的議題。套件選用上其實要極力避免「狀態共享」或「記憶體共享」,一但有不同版本將會視為不一樣的東西,所以溝通機制還是要選用專門用來進行溝通的手段。一但強制微前端的所有套件非得要進行版本一致,那會產生的版本更新議題就會非常腦筋,你就得整個應用程式全部統一升版,那會造成的問題會非常嚴重。除非該溝同的資訊不影響業務邏輯,如快取共用相關,那就可以去共享。
精準的打包— Webpack 的 Tree Shaking
MDN - Tree Shaking
Patterns dev - Tree Shaking